package com.pixplicity.easyprefs.library;

import ohos.app.AbilityContext;
import ohos.app.Context;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

@SuppressWarnings("unused")
public class Prefs {
    private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, Prefs.class.getSimpleName());

    private static final String DEFAULT_SUFFIX = "_preferences";
    private static final String LENGTH = "#LENGTH";
    private static Preferences mPrefs;

    private static Context mContext;

    /**
     * Initialize the Prefs helper class to keep a reference to the Preference for this
     * application the Preference will use the package name of the application as the Key.
     * This method is deprecated please us the new builder.
     *
     * @param context the Application context.
     */
    @Deprecated
    public static void initPrefs(Context context) {
        mContext = context;
        new Builder().setContext(context).build();
    }

    private static void initPrefs(Context context, String prefsName, int mode) {
        mContext = context;
        DatabaseHelper databaseHelper = new DatabaseHelper(mContext);
        mPrefs = databaseHelper.getPreferences(prefsName);
    }

    /**
     * Returns the underlying Preference instance
     *
     * @return an instance of the Preference
     * @throws RuntimeException if Preference instance has not been instantiated yet.
     */
    @SuppressWarnings("WeakerAccess")
    public static Preferences getPreferences() {
        if (mPrefs != null) {
            return mPrefs;
        }
        throw new RuntimeException(
                "Prefs class not correctly instantiated. Please call Builder.setContext().build() in the Application class onCreate.");
    }

    /**
     * Returns a map containing a list of pairs key/value representing the preferences.
     *
     * @return Returns a map
     * @see Preferences#getAll()
     */
    public static Map<String, ?> getAll() {
        return getPreferences().getAll();
    }

    /**
     * Retrieves a stored int value.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getInt(String, int)
     */
    public static int getInt(final String key, final int defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getInt(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return defValue;
    }

    /**
     * Retrieves a stored int value, or 0 if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or 0.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getInt(String, int)
     */
    public static int getInt(final String key) throws IllegalArgumentException {
        try {
            return getPreferences().getInt(key, 0);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return 0;
    }

    /**
     * Retrieves a stored boolean value.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getBoolean(String, boolean)
     */
    public static boolean getBoolean(final String key, final boolean defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getBoolean(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return defValue;
    }

    /**
     * Retrieves a stored boolean value, or false if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or false.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getBoolean(String, boolean)
     */
    public static boolean getBoolean(final String key) throws IllegalArgumentException {
        try {
            return getPreferences().getBoolean(key, false);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return false;
    }

    /**
     * Retrieves a stored long value.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getLong(String, long)
     */
    public static long getLong(final String key, final long defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getLong(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return defValue;
    }

    /**
     * Retrieves a stored long value, or 0 if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or 0.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getLong(String, long)
     */
    public static long getLong(final String key) throws IllegalArgumentException {
        try {
            return getPreferences().getLong(key, 0L);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return 0L;
    }

    /**
     * Returns the double that has been saved as a long raw bits value in the long preferences.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue the double Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getLong(String, long)
     */
    public static double getDouble(final String key, final double defValue) throws IllegalArgumentException {
        try {
            return Double.longBitsToDouble(getPreferences().getLong(key, Double.doubleToLongBits(defValue)));
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return Double.doubleToLongBits(defValue);
    }

    /**
     * Returns the double that has been saved as a long raw bits value in the long preferences.
     * Returns 0 if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or 0.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getLong(String, long)
     */
    public static double getDouble(final String key) throws IllegalArgumentException {
        try {
            return Double.longBitsToDouble(getPreferences().getLong(key, Double.doubleToLongBits(0.0d)));
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return Double.doubleToLongBits(0.0d);
    }

    /**
     * Retrieves a stored float value.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getFloat(String, float)
     */
    public static float getFloat(final String key, final float defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getFloat(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return defValue;
    }

    /**
     * Retrieves a stored float value, or 0 if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or 0.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getFloat(String, float)
     */
    public static float getFloat(final String key) throws IllegalArgumentException {
        try {
            return getPreferences().getFloat(key, 0.0f);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
        return 0.0f;
    }

    /**
     * Retrieves a stored String value.
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValue.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getString(String, String)
     */
    public static String getString(final String key, final String defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getString(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Retrieves a stored String value, or an empty string if the preference does not exist.
     *
     * @param key      The name of the preference to retrieve.
     * @return Returns the preference value if it exists, or "".
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getString(String, String)
     */
    public static String getString(final String key) throws IllegalArgumentException {
        try {
            return getPreferences().getString(key, "");
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Retrieves a Set of Strings as stored by {@link #putStringSet(String, Set)}.
     * <strong>Note that the native implementation of {@link Preferences#getStringSet(String,
     * Set)} does not reliably preserve the order of the Strings in the Set.</strong>
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference values if they exist, or defValues otherwise.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#getStringSet(String, java.util.Set)
     */
    @SuppressWarnings("WeakerAccess")
    public static Set<String> getStringSet(final String key, final Set<String> defValue) throws IllegalArgumentException {
        try {
            return getPreferences().getStringSet(key, defValue);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Retrieves a Set of Strings as stored by {@link #putOrderedStringSet(String, Set)},
     * preserving the original order. Note that this implementation is heavier than the native
     * {@link #getStringSet(String, Set)} method (which does not guarantee to preserve order).
     *
     * @param key      The name of the preference to retrieve.
     * @param defValue Value to return if this preference does not exist.
     * @return Returns the preference value if it exists, or defValues otherwise.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see #getStringSet(String, Set)
     */
    @SuppressWarnings("WeakerAccess")
    public static Set<String> getOrderedStringSet(String key, final Set<String> defValue) throws IllegalArgumentException {
        Preferences prefs = getPreferences();
        try {
            if (prefs.hasKey(key + LENGTH)) {
                LinkedHashSet<String> set = new LinkedHashSet<>();
                int stringSetLength = prefs.getInt(key + LENGTH, -1);
                if (stringSetLength >= 0) {
                    for (int i = 0; i < stringSetLength; i++) {
                        set.add(prefs.getString(key + "[" + i + "]", null));
                    }
                }
                return set;
            }
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
        return defValue;
    }

    /**
     * Stores a long value.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putLong(String, long)
     */
    public static void putLong(final String key, final long value) throws IllegalArgumentException {
        try {
            getPreferences().putLong(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Stores an integer value.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putInt(String, int)
     */
    public static void putInt(final String key, final int value) throws IllegalArgumentException {
        try {
            getPreferences().putInt(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Stores a double value as a long raw bits value.
     *
     * @param key   The name of the preference to modify.
     * @param value The double value to be save in the preferences.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putLong(String, long)
     */
    public static void putDouble(final String key, final double value) throws IllegalArgumentException {
        getPreferences().putLong(key, Double.doubleToRawLongBits(value));
    }

    /**
     * Stores a float value.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putFloat(String, float)
     */
    public static void putFloat(final String key, final float value) throws IllegalArgumentException {
        try {
            getPreferences().putFloat(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Stores a boolean value.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putBoolean(String, boolean)
     */
    public static void putBoolean(final String key, final boolean value) throws IllegalArgumentException {
        try {
            getPreferences().putBoolean(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Stores a String value.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putString(String, String)
     */
    public static void putString(final String key, final String value) throws IllegalArgumentException {
        try {
            getPreferences().putString(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
        }
    }

    /**
     * Stores a Set of Strings.
     * <strong>Note that the native implementation of {@link Preferences#putStringSet(String,
     * Set)} does not reliably preserve the order of the Strings in the Set.</strong>
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#putStringSet(String, java.util.Set)
     */
    @SuppressWarnings("WeakerAccess")
    public static void putStringSet(final String key, final Set<String> value) throws IllegalArgumentException {
        try {
            getPreferences().putStringSet(key, value);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Stores a Set of Strings, preserving the order.
     * Note that this method is heavier that the native implementation {@link #putStringSet(String,
     * Set)} (which does not reliably preserve the order of the Set). To preserve the order of the
     * items in the Set, the Set implementation must be one that as an iterator with predictable
     * order, such as {@link LinkedHashSet}.
     *
     * @param key   The name of the preference to modify.
     * @param value The new value for the preference.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see #putStringSet(String, Set)
     * @see #getOrderedStringSet(String, Set)
     */
    @SuppressWarnings("WeakerAccess")
    public static void putOrderedStringSet(String key, Set<String> value) throws IllegalArgumentException {
        final Preferences editor = getPreferences();
        int stringSetLength = 0;
        try {
            if (mPrefs.hasKey(key + LENGTH)) {
                // First read what the value was
                stringSetLength = mPrefs.getInt(key + LENGTH, -1);
            }
            editor.putInt(key + LENGTH, value.size());
            int i = 0;
            for (String aValue : value) {
                editor.putString(key + "[" + i + "]", aValue);
                i++;
            }
            for (; i < stringSetLength; i++) {
                // Remove any remaining values
                editor.delete(key + "[" + i + "]");
            }
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Removes a preference value.
     *
     * @param key The name of the preference to remove.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#delete(String)
     */
    public static void remove(final String key) throws IllegalArgumentException {
        Preferences prefs = getPreferences();
        try {
            if (prefs.hasKey(key + LENGTH)) {
                int stringSetLength = prefs.getInt(key + LENGTH, -1);
                if (stringSetLength >= 0) {
                    prefs.delete(key + LENGTH);
                    for (int i = 0; i < stringSetLength; i++) {
                        prefs.delete(key + "[" + i + "]");
                    }
                }
            }
            prefs.delete(key);
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Checks if a value is stored for the given key.
     *
     * @param key The name of the preference to check.
     * @return {@code true} if the storage contains this key value, {@code false} otherwise.
     * @throws IllegalArgumentException if key length exceeds 81 char or value exceeds 8192 length.
     * @see Preferences#hasKey(String)
     */
    public static boolean contains(final String key) throws IllegalArgumentException {
        try {
            boolean flag = getPreferences().hasKey(key);
            if(!flag) {
                flag = mPrefs.hasKey(key + LENGTH);
            }

            if(!flag) {
                flag = mPrefs.hasKey(key + "[" + 0 + "]");
            }
            return flag;
        } catch(IllegalArgumentException e) {
            HiLog.error(LABEL_LOG, String.format(Locale.ROOT, "%s", e.getMessage()));
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Removed all the stored keys and values.
     *
     * @return the {@link Preferences} for chaining. The changes have already been committed/applied
     * through the execution of this method.
     * @see Preferences#clear()
     */
    public static Preferences clear() {
        final Preferences editor = getPreferences().clear();
        return editor;
    }

    /**
     * Returns the Editor of the underlying Preferences instance.
     *
     * @return An Editor
     */
    public static Preferences edit() {
        return getPreferences();
    }

    /**
     * Builder class for the EasyPrefs instance. You only have to call this once in the MyApplication
     * onInitialize. And in the rest of the code base you can call Prefs.method name.
     */
    public final static class Builder {

        private String mKey;
        private Context mContext;
        private int mMode = -1;
        private boolean mUseDefault = false;

        /**
         * Set the filename of the Preference instance. Usually this is the application's
         * packagename.xml but it can be modified for migration purposes or customization.
         *
         * @param prefsName the filename used for the Preference
         * @return the {@link com.pixplicity.easyprefs.library.Prefs.Builder} object.
         */
        public Builder setPrefsName(final String prefsName) {
            mKey = prefsName;
            return this;
        }

        /**
         * Set the Context used to instantiate the Preferences
         *
         * @param context the application context
         * @return the {@link com.pixplicity.easyprefs.library.Prefs.Builder} object.
         */
        public Builder setContext(final Context context) {
            mContext = context;
            return this;
        }

        /**
         * Set the mode of the Preference instance.
         *
         * @param mode Operating mode.  Use 0 or {@link Context#MODE_PRIVATE} for the
         *             default operation, {@link Context#MODE_APPEND}
         * @return the {@link com.pixplicity.easyprefs.library.Prefs.Builder} object.
         * @see DatabaseHelper#getPreferences
         */
        @SuppressWarnings({"WorldReadableFiles", "WorldWriteableFiles"})
        public Builder setMode(final int mode) {
            if (mode == AbilityContext.MODE_PRIVATE || mode == AbilityContext.MODE_APPEND) {
                mMode = mode;
            } else {
                throw new RuntimeException("The mode in the Preference can only be set too AbilityContext.MODE_PRIVATE, AbilityContext.MODE_APPEND");
            }
            return this;
        }

        /**
         * Set the default Preference file name. Often the package name of the application is
         * used, the system will append that with _preference.
         *
         * @param defaultSharedPreference true if default SharedPreference name should used.
         * @return the {@link com.pixplicity.easyprefs.library.Prefs.Builder} object.
         */
        @SuppressWarnings("SameParameterValue")
        public Builder setUseDefaultSharedPreference(boolean defaultSharedPreference) {
            mUseDefault = defaultSharedPreference;
            return this;
        }

        /**
         * Initialize the Preference instance to used in the application.
         *
         * @throws RuntimeException if Context has not been set.
         */
        public void build() {
            if (mContext == null) {
                throw new RuntimeException("Context not set, please set context before building the Prefs instance.");
            }

            if (TextUtils.isEmpty(mKey)) {
                mKey = mContext.getBundleName();
            }

            if (mUseDefault) {
                mKey += DEFAULT_SUFFIX;
            }

            if (mMode == -1) {
                mMode = AbilityContext.MODE_PRIVATE;
            }

            Prefs.initPrefs(mContext, mKey, mMode);
        }
    }
}